<template>
  <div
    ref="wrapperRef"
    class="slider-captcha-wrapper display-flex flex-align"
    :style="wrapperStyle"
    @mouseleave="handleDragOver"
    @mousemove="handleDragMoving"
    @mouseup="handleDragOver"
    @touchend="handleDragOver"
    @touchmove="handleDragMoving"
  >
    <captcha-bar
      ref="barRef"
      :back-left="state.backLeft"
      :bar-style="barStyle"
    />
    <captcha-content
      ref="contentRef"
      :content-style="contentStyle"
      :is-passing="state.isPassing"
      :success-text="successText"
      :text
    >
      <template v-if="solts.content" #text>
        <slot :is-passing="state.isPassing" name="content"></slot>
      </template>
    </captcha-content>
    <captcha-action
      ref="actionRef"
      :action-style="actionStyle"
      :is-passing="state.isPassing"
      :back-left="state.backLeft"
      @mousedown="handleDragStart"
      @touchstart="handleDragStart"
    >
      <template v-if="solts.actionIcon" #icon>
        <slot :is-passing="state.isPassing" name="actionIcon"></slot>
      </template>
    </captcha-action>
  </div>
</template>

<script setup lang="ts">
import { reactive, unref, useTemplateRef, watch, watchEffect } from 'vue'
import type {
  WrapperProps,
  WapperEmits,
  WrapperSlots,
  WrapperExposes
} from './types'
import type {
  CaptchaBarInstance,
  CaptchaContentInstance,
  CaptchaActionInstance
} from './index'

const props = withDefaults(defineProps<WrapperProps>(), {
  wrapperStyle: () => ({}),
  barStyle: () => ({}),
  contentStyle: () => ({}),
  successText: '验证通过',
  text: '请按住滑块拖动',
  actionStyle: () => ({}),
  isSlot: false
})
const emits = defineEmits<WapperEmits>()
const models = defineModel<boolean>({ default: false })
const solts = defineSlots<WrapperSlots>()

const initState = {
  endTime: 0, // 结束拖拽时间戳
  isMoving: false, // 是否开始拖拽
  isPassing: false, // 是否通过验证
  moveDistance: 0, // 移动距离
  startTime: 0, // 开始拖拽时间戳
  backLeft: false // 是否返回初始位置
}
const state = reactive({ ...initState })

const wrapperRef = useTemplateRef<HTMLDivElement>('wrapperRef')
const barRef = useTemplateRef<CaptchaBarInstance>('barRef')
const contentRef = useTemplateRef<CaptchaContentInstance>('contentRef')
const actionRef = useTemplateRef<CaptchaActionInstance>('actionRef')

watch(
  () => state.isPassing,
  (newVal) => {
    if (newVal) {
      const time = (state.endTime - state.startTime) / 1000
      emits('success', { isPassing: newVal, time: time.toFixed(1) })
      models.value = newVal
    }
  }
)

watchEffect(() => {
  state.isPassing = !!models.value
})

const handleDragStart = (e: MouseEvent | TouchEvent) => {
  if (state.isPassing) return
  if (!actionRef.value) return
  emits('start', e)
  const actionLeft = actionRef.value.getStyle()?.left.replace('px', '') || '0'
  const left = Number.parseInt(actionLeft, 10)
  state.moveDistance = getEventPageX(e) - left
  state.startTime = Date.now()
  state.isMoving = true
}

const handleDragMoving = (e: MouseEvent | TouchEvent) => {
  if (state.isMoving) {
    const actionEl = unref(actionRef)
    const barEl = unref(barRef)
    if (!actionEl || !barEl) return
    const { actionWidth, offset, wrapperWidth } = getOffset(actionEl.getEl())
    const moveX = getEventPageX(e) - state.moveDistance
    emits('move', { e, moveDistance: state.moveDistance, moveX })
    if (moveX > 0 && moveX <= offset) {
      actionEl.setLeft(`${moveX}px`)
      barEl.setWidth(`${moveX + actionWidth / 2}px`)
    } else if (moveX > offset) {
      actionEl.setLeft(`${wrapperWidth - actionWidth}px`)
      barEl.setWidth(`${wrapperWidth - actionWidth / 2}px`)
      if (!props.isSlot) {
        checkPass()
      }
    }
  }
}

const handleDragOver = (e: MouseEvent | TouchEvent) => {
  if (state.isMoving && !state.isPassing) {
    emits('end', e)
    const actionEl = unref(actionRef)
    const barEl = unref(barRef)
    if (!actionEl || !barEl) return
    const moveX = getEventPageX(e) - state.moveDistance
    const { actionWidth, offset, wrapperWidth } = getOffset(actionEl.getEl())
    if (moveX < offset) {
      if (props.isSlot) {
        setTimeout(() => {
          if (models.value) {
            const contentEl = unref(contentRef)
            const width = Number.parseInt(barEl.getEl()?.style.width || '0')
            contentEl?.setWidth(`${width}px`)
          } else {
            resume()
          }
        }, 0)
      } else {
        resume()
      }
    } else {
      actionEl.setLeft(`${wrapperWidth - actionWidth}px`)
      barEl.setWidth(`${wrapperWidth - actionWidth / 2}px`)
      checkPass()
    }
    state.isMoving = false
  }
}

const getEventPageX = (e: MouseEvent | TouchEvent) => {
  if ('pageX' in e) return e.pageX
  if ('touches' in e && e.touches[0]) return e.touches[0].pageX
  return 0
}

const getOffset = (actionEl: HTMLDivElement | null) => {
  const wrapperWidth = wrapperRef.value?.offsetWidth ?? 220
  const actionWidth = actionEl?.offsetWidth ?? 40
  const offset = wrapperWidth - actionWidth - 6
  return { actionWidth, offset, wrapperWidth }
}

const checkPass = () => {
  if (props.isSlot) {
    resume()
    return
  }
  state.endTime = Date.now()
  state.isPassing = true
  state.isMoving = false
}

const resume = () => {
  Object.assign(state, initState)
  const actionEl = unref(actionRef)
  const barEl = unref(barRef)
  const contentEl = unref(contentRef)
  if (!actionEl || !barEl || !contentEl) return
  contentEl.setWidth('100%')
  state.backLeft = true
  setTimeout(() => {
    state.backLeft = false
    actionEl.setLeft('0')
    barEl.setWidth('0')
  }, 300)
}

defineExpose<WrapperExposes>({ resume })

defineOptions({
  name: 'CoreSliderCaptcha'
})
</script>

<style scoped lang="scss">
.slider-captcha-wrapper {
  position: relative;
  width: 100%;
  height: 100%;
  overflow: hidden;
  background-color: var(--core-bg-color-05);
  border: 1px solid var(--core-border-color-01);
  border-radius: 6px;
}
</style>
